// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2016-2019, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * memctrl driver - Driver of LomboTech Memctrl Controller
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/clk-private.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/pm_clock.h>
#include <mach/debug.h>
#include <mach/common.h>
#include "memctrl.h"
#include "memctrl_conf.h"
#include "memctrl_dev.h"


/* redefine mode name, for PTR_XXX */
#undef MOD_NAME
#define MOD_NAME	"MEMCTRl"

static struct global_data globaldata;
static struct channel_data totaldata;
static struct channel_data channeldata;
#if defined(CONFIG_ARCH_LOMBO_N7V1)
static void __iomem *mctrl_base;
#else
static void __iomem *noc;
#endif

static void __iomem *sarb;
void __iomem *lombo_get_noc_addr(void)
{
#if defined(CONFIG_ARCH_LOMBO_N7V1)
	return mctrl_base;
#else
	return noc;
#endif
}

void __iomem *lombo_get_sarb_addr(void)
{
	return sarb;
}

static u32 memctrl_div64(u64 n, u64 base)
{
	u32 res = 0;

	while (n > base) {
		n -= base;
		res++;
	}
	do_div(base, 2);
	if (n > base)
		res++;
	return res;
}

static void mop(struct lombo_memctrl *mctrl, int ch_id, const char *fmt, ...)
{
	va_list args;
	size_t length;

	va_start(args, fmt);
	if (ch_id == 0) {
		length = vsnprintf(mctrl->log_buf0 + mctrl->log_offset0,
				PAGE_SIZE - 1, fmt, args);
		mctrl->log_offset0 += length;
	} else {
		length = vsnprintf(mctrl->log_buf1 + mctrl->log_offset1,
				PAGE_SIZE - 1, fmt, args);
		mctrl->log_offset1 += length;
	}
	va_end(args);
}

static void memctrl_start(struct lombo_memctrl *mctrl, int c_id)
{
	csp_memctrl_interrupt_enable(mctrl->reg[c_id], true);
	csp_memctrl_enable(mctrl->reg[c_id], true);
#ifdef CONFIG_ARCH_LOMBO_N7V3
	csp_memctrl_start_hvc(true);
#endif
}

static void memctrl_stop(struct lombo_memctrl *mctrl, int c_id)
{
	csp_memctrl_interrupt_enable(mctrl->reg[c_id], false);
	csp_memctrl_enable(mctrl->reg[c_id], false);
#ifdef CONFIG_ARCH_LOMBO_N7V3
	csp_memctrl_start_hvc(false);
#endif
}

static void memctrl_set_data_unit(struct lombo_memctrl *mctrl,
					enum memctrl_data_unit data, int c_id)
{
	csp_memctrl_set_data_unit(mctrl->reg[c_id], data);
}

static void memctrl_set_sample(struct lombo_memctrl *mctrl,
					enum memctrl_sample sample, int c_id)
{
	csp_memctrl_set_sample(mctrl->reg[c_id], sample);
}

static u32 memctrl_get_cycle(struct lombo_memctrl *mctrl, int c_id)
{
	return csp_memctrl_get_cycle(mctrl->reg[c_id]);
}

static void memctrl_get_global_data(struct lombo_memctrl *mctrl,
					struct global_data *data, int c_id)
{
	data->total_rd_size =
		csp_memctrl_get_total_rd_data_size(mctrl->reg[c_id]);
	data->total_wr_size =
		csp_memctrl_get_total_wr_data_size(mctrl->reg[c_id]);

	/* NOTE: total data maybe overflow if store in u32. what was worse,
	 * if total data register is overflow, which interrupt would not rasie,
	 * therefore, calculate it by software rather then hardware.
	 */
	data->total_size =
		(u64)data->total_rd_size + (u64)data->total_wr_size;

	data->total_rd_peak =
		csp_memctrl_get_total_rd_peak(mctrl->reg[c_id]);
	data->total_wr_peak =
		csp_memctrl_get_total_wr_peak(mctrl->reg[c_id]);
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	data->total_rdwr_peak =
		csp_memctrl_get_total_rdwr_peak(mctrl->reg[c_id]);
#endif
}

static void memctrl_get_channel_data(struct lombo_memctrl *mctrl,
		int channel, struct channel_data *data, int c_id)
{
	data->rd_size =
		csp_memctrl_get_rd_data_size(mctrl->reg[c_id], channel);
	data->wr_size =
		csp_memctrl_get_wr_data_size(mctrl->reg[c_id], channel);

	data->rd_peak =
		csp_memctrl_get_rd_peak_data_size(mctrl->reg[c_id], channel);
	data->wr_peak =
		csp_memctrl_get_wr_peak_data_size(mctrl->reg[c_id], channel);
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	data->rdwr_peak =
		csp_memctrl_get_rdwr_peak_data_size(mctrl->reg[c_id], channel);
#endif

	data->rd_num =
		csp_memctrl_get_rd_cmd_num(mctrl->reg[c_id], channel);
	data->wr_num =
		csp_memctrl_get_wr_cmd_num(mctrl->reg[c_id], channel);

	data->rd_latency =
		csp_memctrl_get_rd_latency(mctrl->reg[c_id], channel)
				* LAT_CYCTLES / mctrl->lat_div_us;
	data->wr_latency =
		csp_memctrl_get_wr_latency(mctrl->reg[c_id], channel)
				* LAT_CYCTLES / mctrl->lat_div_us;
}

static void memctrl_get_ddr_info(struct lombo_memctrl *mctrl, int c_id)
{
	u32 freq, bit_width, bandw_theo, bandw_effec_65, bandw_effec_70;
	int i;
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	int step = 7;
	char *line = "---------";
#else
	int step = 6;
	char *line = "----------";
#endif

#if defined(CONFIG_ARCH_LOMBO_N9V1)
	freq = clk_get_rate(mctrl->sdram_clk) * 2 / 1000000;
#else
	freq = clk_get_rate(mctrl->sdram_clk) / 2 / 1000000;
#endif

	if (!csp_ddr_get_bit_width(mctrl->ddr_bitbase[c_id]))
		bit_width = 32;
	else
		bit_width = 16;

	bandw_theo = freq * 2 * bit_width / 8;
	bandw_effec_65 = bandw_theo * 65 / 100;
	bandw_effec_70 = bandw_theo * 70 / 100;

	mop(mctrl, c_id, "\n");
	for (i = 0; i < step; ++i)
		mop(mctrl, c_id, "----------");
	mop(mctrl, c_id, "DRAM INFO", c_id);
	for (i = 0; i < step; ++i)
		mop(mctrl, c_id, line);
	mop(mctrl, c_id, "\n");
	mop(mctrl, c_id, "DDR frequence: %d MHz\tDDR bit width:%d\t"
		"Theoretical bandwidth:%d MB/s\t"
		"Effective bandwidth:%d MB/s - %d MB/s",
		freq, bit_width, bandw_theo, bandw_effec_65, bandw_effec_70);
}

static void memctrl_dump_start(struct lombo_memctrl *mctrl, int c_id)
{
	int i;
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	int step = 7;
#else
	int step = 6;
#endif

	memctrl_get_ddr_info(mctrl, c_id);

#if defined(CONFIG_ARCH_LOMBO_N7V3)
	mop(mctrl, c_id, "\nothers master include: IVX, CE, I2C4");
#endif
	mop(mctrl, c_id, "\n");
	for (i = step; i > 0; --i)
		mop(mctrl, c_id, "----------");
	mop(mctrl, c_id, "--CH[%d]--", c_id);
	for (i = step; i > 0; --i)
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
		mop(mctrl, c_id, "---------");
#else
		mop(mctrl, c_id, "----------");
#endif
	mop(mctrl, c_id, "\n");

#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	mop(mctrl, c_id,
	"  %-8s %10s %5s %10s %10s %10s %10s %5s %10s %10s %10s %10s %5s %10s\n",
		"MASTER", "READ", "%R", "R_PEAK", "R_NUM", "R_LAT", "WRITE",
		"%W", "W_PEAK", "W_NUM", "W_LAT", "RW", "%RW", "RW_PEAK");
#else
	mop(mctrl, c_id,
	"  %-8s %10s %5s %10s %10s %10s %10s %5s %10s %10s %10s %10s %5s\n",
		"MASTER", "READ", "%R", "R_PEAK", "R_NUM", "R_LAT", "WRITE",
		"%W", "W_PEAK", "W_NUM", "W_LAT", "RW", "%RW");
#endif
}

static void memctrl_dump_channel(const char *name, int c_id,
				struct global_data *gdata,
				struct channel_data *cdata,
				struct lombo_memctrl *mctrl)
{
	const struct data_unit *data_units =
				mctrl->data_unit_array->data_units;
	const struct cfg_unit *flaw_units =
				mctrl->flaw_unit_array->flaw_units;
	u32 data_unit_size = data_units[mctrl->size_unit].size;
	u64 rd_size = cdata->rd_size * flaw_units[mctrl->flaw_unit].size;
	u64 rd_percent = 0;
	u64 rd_peak = cdata->rd_peak * flaw_units[mctrl->flaw_unit].size;
	u64 wr_size = cdata->wr_size * flaw_units[mctrl->flaw_unit].size;
	u64 wr_percent = 0;
	u64 wr_peak = cdata->wr_peak * flaw_units[mctrl->flaw_unit].size;
	u64 rw_percent = 0;
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	u64 rdwr_peak = cdata->rdwr_peak * flaw_units[mctrl->flaw_unit].size;
#endif

	do_div(rd_size, data_unit_size);
	if (gdata->total_rd_size != 0) {
		rd_percent = cdata->rd_size * 100;
		do_div(rd_percent, gdata->total_rd_size);
	}
	do_div(rd_peak, data_unit_size);
	do_div(wr_size, data_unit_size);
	if (gdata->total_wr_size != 0) {
		wr_percent = cdata->wr_size * 100;
		do_div(wr_percent, gdata->total_wr_size);
	}
	do_div(wr_peak, data_unit_size);
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	do_div(rdwr_peak, data_unit_size);
#endif
	if (gdata->total_size != 0) {
		rw_percent = (cdata->rd_size + cdata->wr_size) * 100;
		rw_percent = memctrl_div64(rw_percent, gdata->total_size);
	}

#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	mop(mctrl, c_id,
	"  %-8s %10llu %5d %10llu %10llu %10llu %10llu %5d %10llu %10llu %10llu %10llu %5d %10llu\n",
	  name, rd_size, rd_percent, rd_peak, cdata->rd_num, cdata->rd_latency,
	  wr_size, wr_percent, wr_peak, cdata->wr_num, cdata->wr_latency,
	  (rd_size + wr_size), rw_percent, rdwr_peak);
#else
	mop(mctrl, c_id,
	"  %-8s %10llu %5d %10llu %10llu %10llu %10llu %5d %10llu %10llu %10llu %10llu %5d\n",
	  name, rd_size, rd_percent, rd_peak, cdata->rd_num, cdata->rd_latency,
	  wr_size, wr_percent, wr_peak, cdata->wr_num, cdata->wr_latency,
	  (rd_size + wr_size), rw_percent);
#endif
}

static void memctrl_channel_trace(struct channel_data *gdata,
					struct channel_data *cdata)
{
	gdata->rd_num += cdata->rd_num;
	gdata->rd_latency += cdata->rd_latency;
	gdata->wr_num += cdata->wr_num;
	gdata->wr_latency += cdata->wr_latency;
	gdata->rd_size += cdata->rd_size;
	gdata->wr_size += cdata->wr_size;
}

static void memctrl_dump_total(struct global_data *gdata, int c_id,
					struct channel_data *cdata,
					struct lombo_memctrl *mctrl)
{
	const struct data_unit *data_units =
				mctrl->data_unit_array->data_units;
	const struct cfg_unit *flaw_units =
				mctrl->flaw_unit_array->flaw_units;
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	int i = 14;
#else
	int i = 13;
#endif

	u32 data_unit_size = data_units[mctrl->size_unit].size;

	u64 total_rd_size =
		(u64)gdata->total_rd_size * flaw_units[mctrl->flaw_unit].size;
	u64 rd_percent = 0;

	u64 total_wr_size =
		(u64)gdata->total_wr_size * flaw_units[mctrl->flaw_unit].size;
	u64 wr_percent = 0;

	u64 total_rw_size =
		(u64)gdata->total_size * flaw_units[mctrl->flaw_unit].size;
	u64 rw_percent = 0;

	u64 rd_peak =
		(u64)gdata->total_rd_peak * flaw_units[mctrl->flaw_unit].size;
	u64 wr_peak =
		(u64)gdata->total_wr_peak * flaw_units[mctrl->flaw_unit].size;
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	u64 rdwr_peak =
		(u64)gdata->total_rdwr_peak * flaw_units[mctrl->flaw_unit].size;
#endif

	/* Split line */
	while (i--)
		mop(mctrl, c_id, "----------");
	mop(mctrl, c_id, "\n");


	do_div(total_rd_size, data_unit_size);
	if (gdata->total_rd_size != 0) {
		rd_percent = cdata->rd_size * 100;
		do_div(rd_percent, gdata->total_rd_size);
	}

	do_div(total_wr_size, data_unit_size);
	if (gdata->total_wr_size != 0) {
		wr_percent = cdata->wr_size * 100;
		do_div(wr_percent, gdata->total_wr_size);
	}

	do_div(total_rw_size, data_unit_size);
	if (gdata->total_size != 0) {
		rw_percent = (cdata->rd_size + cdata->wr_size) * 100;
		rw_percent = memctrl_div64(rw_percent, gdata->total_size);
	}

	do_div(rd_peak, data_unit_size);
	do_div(wr_peak, data_unit_size);
#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	do_div(rdwr_peak, data_unit_size);
#endif

#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	mop(mctrl, c_id,
	"  %-8s %10llu %5d %10llu %10llu %10llu %10llu %5d %10llu %10llu %10llu %10llu %5d %10llu\n",
		"total", total_rd_size, rd_percent, rd_peak,
		cdata->rd_num, cdata->rd_latency,
		total_wr_size, wr_percent, wr_peak,
		cdata->wr_num, cdata->wr_latency,
		total_rw_size, rw_percent, rdwr_peak);
#else
	mop(mctrl, c_id,
	"  %-8s %10llu %5d %10llu %10llu %10llu %10llu %5d %10llu %10llu %10llu %10llu %5d\n",
		"total", total_rd_size, rd_percent, rd_peak,
		cdata->rd_num, cdata->rd_latency,
		total_wr_size, wr_percent, wr_peak,
		cdata->wr_num, cdata->wr_latency,
		total_rw_size, rw_percent);
#endif
}

static void memctrl_dump_end(struct lombo_memctrl *mctrl, int c_id)
{
	const struct data_unit *data_units =
				mctrl->data_unit_array->data_units;
	const struct cfg_unit *sample_units =
				mctrl->sample_unit_array->sample_units;
	const char *lat_unit = "us";
	char sample_unit[10], time_unit[10], data_unit[10];
	const char *sample_format = "%s/%d%s";
	const char *time_format = "/%d%s";
	const char *data_format = "%s%s";

	snprintf(time_unit, sizeof(time_unit), time_format,
	sample_units[mctrl->sample_unit].size * mctrl->actual_time[c_id]
			/ sample_units[mctrl->sample_unit].div,
			sample_units[mctrl->sample_unit].unit);

	snprintf(data_unit, sizeof(data_unit), data_format,
	data_units[mctrl->size_unit].name, time_unit);

	snprintf(sample_unit, sizeof(sample_unit), sample_format,
			data_units[mctrl->size_unit].name,
			sample_units[mctrl->sample_unit].size
			/ sample_units[mctrl->sample_unit].div,
			sample_units[mctrl->sample_unit].unit);

#if defined(CONFIG_ARCH_LOMBO_N7V5) || defined(CONFIG_ARCH_LOMBO_N5V1) \
	|| defined(CONFIG_ARCH_LOMBO_N7V7)
	mop(mctrl, c_id,
	"  %-8s %10s %5s %10s %10s %10s %10s %5s %10s %10s %10s %10s %5s %10s\n",
		"unit", data_unit, "%", sample_unit, time_unit, lat_unit,
		data_unit, "%", sample_unit, time_unit, lat_unit, data_unit,
		"%", sample_unit);
#else
	mop(mctrl, c_id,
	"  %-8s %10s %5s %10s %10s %10s %10s %5s %10s %10s %10s %10s %5s\n",
		"unit", data_unit, "%", sample_unit, time_unit, lat_unit,
		data_unit, "%", sample_unit, time_unit, lat_unit, data_unit,
		"%");
#endif
	mop(mctrl, c_id, "\n");
}

#if defined(CONFIG_ARCH_LOMBO_N7V3)
/**
 * memctrl_get_IVX_CE_HVC_I2C4_data: get total r/w size of IVX_CE_HVC_I2C4
 * @cdata: channel data, there is a virtual channel include IVX_CE_HVC_I2C4
 * @tdata: total data exclude IVX_CE_HVC_I2C4
 * @gdata: total data read from register, include IVX_CE_HVC_I2C4
 *
 * FIXME: IVX_CE_HVC_I2C4 channel data have not statistic in hardware, so
 *	  we need to use total data subtract others channel data to get
 *	  IVX_CE_HVC_I2C4 data. but in this way, we can't get IVX/CE/HVC/I2C4
 *	  channel data in detail.
 */
static void memctrl_get_IVX_CE_HVC_I2C4_data(struct lombo_memctrl *mctrl,
		struct channel_data *cdata, struct channel_data *tdata,
		struct global_data *gdata, u32 channel)
{
	struct clk *hvc_clk;
	const struct cfg_unit *flaw_units =
				mctrl->flaw_unit_array->flaw_units;

	cdata->rd_latency = 0;
	cdata->rd_num = 0;
	cdata->rd_peak = 0;
	cdata->wr_latency = 0;
	cdata->wr_num = 0;
	cdata->wr_peak = 0;
	cdata->rd_size = 0;
	cdata->wr_size = 0;
	if (channel == MEMCTRL_HVC) {
		hvc_clk = clk_get(mctrl->miscdev.this_device,
						"ahb_hvc_s_gate");
		if (__clk_is_enabled(hvc_clk)) {
			cdata->rd_size = csp_memctrl_get_hvc_rd_data() /
					flaw_units[mctrl->flaw_unit].size;
			cdata->wr_size = csp_memctrl_get_hvc_wr_data() /
					flaw_units[mctrl->flaw_unit].size;
		}
	} else if (channel == MEMCTRL_I2C4) {
		if (gdata->total_rd_size > tdata->rd_size)
			cdata->rd_size = gdata->total_rd_size - tdata->rd_size;
		if (gdata->total_wr_size > tdata->wr_size)
			cdata->wr_size = gdata->total_wr_size - tdata->wr_size;
	} else
		return;
}
#endif

static void memctrl_dump_once(struct lombo_memctrl *mctrl)
{
	const struct cfg_unit *flaw_units =
				mctrl->flaw_unit_array->flaw_units;
	const struct cfg_unit *sample_units =
				mctrl->sample_unit_array->sample_units;
	const struct channel_info *master_channels =
				mctrl->channel_info_array->master_channels;
	struct channel_data *cdata = &channeldata;
	struct channel_data *total_data = &totaldata;
	struct global_data *gdata = &globaldata;
	const struct channel_info *cinfo;
	unsigned long flags;
	int i, j;

	/* make the basic configuration */
	for (i = 0; i < mctrl->ch_number; ++i) {
		memctrl_set_data_unit(mctrl,
		flaw_units[mctrl->flaw_unit].cfg, i);
		memctrl_set_sample(mctrl,
		sample_units[mctrl->sample_unit].cfg, i);
	}

	/* turn on data statistics */
	for (i = 0; i < mctrl->ch_number; ++i)
		memctrl_start(mctrl, i);

	/* use the last dram channel as the time base */
	while (1) {
		mctrl->actual_time[i - 1] =
			memctrl_get_cycle(mctrl, i - 1);
		if (mctrl->time <= mctrl->actual_time[i - 1])
			break;
		if (mctrl->overflow.which)
			break;
	}

	/**
	 * if the last channel has reached the 'mctrl->time' cycles
	 * the rest channels must had reached the cycles too.
	 *
	 * here we' better do not let the current thread to be preempt
	 * or interrupted by interrupt.
	 */
	spin_lock_irqsave(&mctrl->lock, flags);
	for (i = i - 1; i >= 0; --i) {
		mctrl->actual_time[i] = memctrl_get_cycle(mctrl, i);
		/* turn off data statistics */
		memctrl_stop(mctrl, i);
	}
	spin_unlock_irqrestore(&mctrl->lock, flags);

	/* Dump each channel */
	for (j = 0; j < mctrl->ch_number; ++j) {
		memset(total_data, 0, sizeof(*total_data));
		memctrl_get_global_data(mctrl, gdata, j);
		memctrl_dump_start(mctrl, j);
		for (i = 0; i < mctrl->channel_info_array->array_size; i++) {
			cinfo = &master_channels[i];
#if defined(CONFIG_ARCH_LOMBO_N7V3)
			if (cinfo->hwnum == MEMCTRL_IVX ||
				cinfo->hwnum == MEMCTRL_CE)
				continue;
			if (cinfo->hwnum > MEMCTRL_VGSS) {
				memctrl_get_IVX_CE_HVC_I2C4_data(mctrl, cdata,
					total_data, gdata, cinfo->hwnum);
				if (cinfo->hwnum == MEMCTRL_HVC) {
					memctrl_channel_trace(total_data,
								cdata);
					memctrl_dump_channel(cinfo->name,
								j, gdata,
								cdata, mctrl);
				}
				if (cinfo->hwnum == MEMCTRL_I2C4) {
					memctrl_channel_trace(total_data,
								cdata);
					memctrl_dump_channel("Others",
								j, gdata,
								cdata, mctrl);
				}
			} else {
				memctrl_get_channel_data(mctrl, cinfo->hwnum,
							cdata, j);
				memctrl_channel_trace(total_data, cdata);
				memctrl_dump_channel(cinfo->name, j, gdata,
								cdata, mctrl);
			}

#else
			memctrl_get_channel_data(mctrl, cinfo->hwnum, cdata, j);
			memctrl_channel_trace(total_data, cdata);
			memctrl_dump_channel(cinfo->name, j, gdata,
							cdata, mctrl);
#endif
		}
		memctrl_dump_total(gdata, j, total_data, mctrl);
		memctrl_dump_end(mctrl, j);
	}
}

static void memctrl_dump(struct lombo_memctrl *mctrl)
{
	const struct data_unit *data_units =
				mctrl->data_unit_array->data_units;
	const struct cfg_unit *flaw_units =
				mctrl->flaw_unit_array->flaw_units;
	const struct cfg_unit *sample_units =
				mctrl->sample_unit_array->sample_units;
	PRT_DBG(
	"time:%d, period:%d, count:%d, sample:%s, flaw_unit:%s, data_unit:%s\n",
		mctrl->time, mctrl->period, mctrl->count,
		sample_units[mctrl->sample_unit].name,
		flaw_units[mctrl->flaw_unit].name,
		data_units[mctrl->size_unit].name);

	/* Not support now, should be false */
	if (mctrl->op == OP_FILE) {
		mctrl->pfile = filp_open(mctrl->file, O_WRONLY | O_CREAT, 0644);
		if (!mctrl->pfile) {
			PRT_ERR("fail to open file: %s\n", mctrl->file);
			return;
		}
		mctrl->fpos = 0;
	}
	/* Reset log buffer index */
	mctrl->log_offset0 = 0;
	mctrl->log_offset1 = 0;

	memctrl_dump_once(mctrl);

	/* Not support now, should be false */
	if (mctrl->op == OP_FILE)
		filp_close(mctrl->pfile, NULL);
}

static irqreturn_t lombo_memctrl_irq(int irq, void *dev_id)
{
	struct lombo_memctrl *mctrl = (struct lombo_memctrl *)dev_id;
	u32 latency, i;
	int schedule = 0;

	for (i = 0; i < mctrl->ch_number; ++i) {
		/* stop memctrl statistic first */
		memctrl_stop(mctrl, i);
		if (csp_memctrl_is_cycle_overflow(mctrl->reg[i])) {
			mctrl->overflow.which |= CYCLE_OVERFLOW;
			mctrl->overflow.ch_number = i;
		}

		if (csp_memctrl_is_data_overflow(mctrl->reg[i])) {
			mctrl->overflow.which |= DATA_OVERFLOW;
			mctrl->overflow.ch_number = i;
		}

		csp_memctrl_clear_status(mctrl->reg[i]);

		latency = csp_memctrl_get_rd_latency_status(mctrl->reg[i]);
		if (latency) {
			mctrl->overflow.which |= READ_OVERFLOW;
			mctrl->overflow.ch_number = i;
			mctrl->overflow.status = latency;
		}
		csp_memctrl_clear_rd_latency_status(mctrl->reg[i]);

		latency = csp_memctrl_get_wr_latency_status(mctrl->reg[i]);
		if (latency) {
			mctrl->overflow.which |= WRITE_OVERFLOW;
			mctrl->overflow.ch_number = i;
			mctrl->overflow.status = latency;
		}
		csp_memctrl_clear_wr_latency_status(mctrl->reg[i]);
	}

	for (i = 0; i < mctrl->ch_number; i++) {
		/* check DRAM monitor status */
		if (mctrl->mnt[i]) {
			schedule |= check_global_monitor(mctrl->mnt[i]);
			schedule |= check_space_monitor(mctrl->mnt[i]);
			if (schedule)
				schedule_work(&mctrl->mnt[i]->update);
		}
	}

	return IRQ_HANDLED;
}

static u32 memctrl_help_overflow(char *buf, struct overfolw_info *overflow)
{
	u32 count;
	ssize_t offset = 1;

	if (CYCLE_OVERFLOW & overflow->which) {
		count = snprintf(buf + offset, PAGE_SIZE - offset,
			"ch[%d]:%s%s\n", overflow->ch_number,
			" cycle overflow!! Statistics may be incorrect,",
			" you can decrease sample time unit");
		offset += count;
	}
	if (DATA_OVERFLOW & overflow->which) {
		count = snprintf(buf + offset, PAGE_SIZE - offset,
			"ch[%d]:%s%s\n", overflow->ch_number,
			" data overflow!! Statistics may be incorrect,",
			" you can increase data unit");
		offset += count;
	}
	if (READ_OVERFLOW & overflow->which) {
		count = snprintf(buf + offset, PAGE_SIZE - offset,
			"ch[%d]: read latency overflow!! status:0x%x\n",
			overflow->ch_number, overflow->status);
		offset += count;
	}
	if (WRITE_OVERFLOW & overflow->which) {
		count = snprintf(buf + offset, PAGE_SIZE - offset,
			"ch[%d]: write latency overflow!! status:0x%x\n",
			overflow->ch_number, overflow->status);
		offset += count;
	}
	return offset;
}

ssize_t __memctrl_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	int count;
	static int flag;
	struct lombo_memctrl *mctrl = dev_get_drvdata(dev);

	mctrl->overflow.which = NO_OVERFLOW;
	mctrl->running = true;
	memctrl_dump(mctrl);
	mctrl->running = false;

	if (mctrl->overflow.which) {
		buf[0] = '$';
		count = memctrl_help_overflow(buf, &mctrl->overflow);
		mctrl->overflow.which = NO_OVERFLOW;
	} else if (mctrl->log_offset1) {
		if (flag == 0) {
			buf[0] = '#';
			count = snprintf(buf + 1,
			PAGE_SIZE, "%s", mctrl->log_buf0);
			flag = 1;
		} else {
			buf[0] = '$';
			count = snprintf(buf + 1,
			PAGE_SIZE, "%s", mctrl->log_buf1);
			flag = 0;
		}
	} else {
		buf[0] = '$';
		count = snprintf(buf + 1, PAGE_SIZE, "%s", mctrl->log_buf0);
	}

	return count + 1;
}

static const struct file_operations lombo_memctrl_fops = {
	.open		= NULL,
	.release	= NULL,
	.write		= NULL,
	.read		= NULL,
	.llseek		= NULL,
};

static int lombo_memctrl_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct resource *res;
	struct lombo_memctrl *mctrl;
	struct clk *sdram_bw_clk, *mem_axi_clk, *sdram_clk;
	int ret, i;

	/* allocate the lombo_memctrl */
	mctrl = devm_kzalloc(dev, sizeof(*mctrl), GFP_KERNEL);
	if (!mctrl) {
		PRT_ERR("failed to alloc memory\n");
		return -ENOMEM;
	}

	mctrl->log_buf0 = devm_kzalloc(dev, PAGE_SIZE, GFP_KERNEL);
	if (!mctrl->log_buf0) {
		kfree(mctrl);
		PRT_ERR("failed to alloc memory\n");
		return -ENOMEM;
	}

	mctrl->log_buf1 = devm_kzalloc(dev, PAGE_SIZE, GFP_KERNEL);
	if (!mctrl->log_buf1) {
		kfree(mctrl->log_buf0);
		kfree(mctrl);
		PRT_ERR("failed to alloc memory\n");
		return -ENOMEM;
	}
	/**
	 * here we need to check how many dram channels the system
	 * has supported
	 */
#ifdef CONFIG_ARCH_LOMBO_N7V1
	mctrl->ch_number = 1;
#else
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	noc = devm_ioremap_resource(dev, res);
	if (IS_ERR(noc)) {
		PRT_ERR("remap noc error\n");
		return -EINVAL;
	}
	mctrl->noc = noc;
#endif

#if defined(CONFIG_ARCH_LOMBO_N7V3) || defined(CONFIG_ARCH_LOMBO_N7V5) \
	|| defined(CONFIG_ARCH_LOMBO_N5V1) || defined(CONFIG_ARCH_LOMBO_N7V7)\
	|| defined(CONFIG_ARCH_LOMBO_N9V3)
	/*
	 * n7v3/n7v5/n9v3 only have one channel,
	 * and noc not include config reg
	 */
	mctrl->ch_number = 1;
#elif defined(CONFIG_ARCH_LOMBO_N9V1)
	/* for n9v1 */
	if ((readl(noc + 0x10) & CHANNEL_CONF_BIT_MASK) == 0x40000000)
		/* only support ch0 */
		mctrl->ch_number = 1;
	else if ((readl(noc + 0x10) & CHANNEL_CONF_BIT_MASK) == 0xc0000000)
		/* support ch1 and ch0 */
		mctrl->ch_number = 2;
	else {
		/* not supported yet! */
		PRT_ERR("make the wrong channel number configuration\n");
		return -EINVAL;
	}
#endif

	/* then read the sub arbiter prioprity */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	mctrl->sub_arbiter_regbase = devm_ioremap_resource(dev, res);
	if (IS_ERR(mctrl->sub_arbiter_regbase)) {
		PRT_ERR("remap sub arbiter error\n");
		return -EINVAL;
	}

	/* for lombo_memctrl_bw_limit using by other module  */
	sarb = mctrl->sub_arbiter_regbase;

	for (i = 0; i < mctrl->ch_number; ++i) {
		res = platform_get_resource(pdev, IORESOURCE_MEM, i + 2);
		mctrl->reg[i] = devm_ioremap_resource(dev, res);
		if (IS_ERR(mctrl->reg[i])) {
			PRT_ERR("remap reg[%d] error\n", i + 2);
			return PTR_ERR(mctrl->reg[i]);
		}
	}

#ifdef CONFIG_ARCH_LOMBO_N7V1
	/* store memctrl address for bwctrl */
	mctrl_base = mctrl->reg[0];
	/* store priority reg base */
	res = platform_get_resource(pdev, IORESOURCE_MEM, i + 2);
	mctrl->reg[i] = devm_ioremap_resource(dev, res);
	if (IS_ERR(mctrl->reg[i])) {
		PRT_ERR("remap reg[%d] error\n", i + 2);
		return PTR_ERR(mctrl->reg[i]);
	}
	/* N7V1 ddr_bitbase*/
	mctrl->ddr_bitbase[0] = mctrl->reg[i];
#else
	for (i = 0; i < mctrl->ch_number; ++i) {
		/* for get ddr information */
		res = platform_get_resource(pdev, IORESOURCE_MEM, i + 4);
		mctrl->ddr_bitbase[i] = devm_ioremap_resource(dev, res);
		if (IS_ERR(mctrl->ddr_bitbase[i])) {
			PRT_ERR("remap ddr_bitbase error\n");
			return PTR_ERR(mctrl->ddr_bitbase[i]);
		}
	}
#endif
	/* Default parameters */
	mctrl->time = 1000;
	mctrl->period = 1;
	mctrl->count = 1;
	mctrl->size_unit = 1;
	mctrl->flaw_unit = MEMCTRL_UNIT_1KB;
	mctrl->sample_unit = MEMCTRL_SAMPLE_1MS;
	mctrl->op = OP_CONSOLE;
	memset(mctrl->file, 0, sizeof(mctrl->file));
	mctrl->data_unit_array = mctrl_dev_get_data_units();
	mctrl->flaw_unit_array = mctrl_dev_get_flaw_units();
	mctrl->sample_unit_array = mctrl_dev_get_sample_units();
	mctrl->channel_info_array = mctrl_dev_get_master_channels();
	mctrl->master_prio_array = mctrl_dev_get_memtrl_master_prio();
	dev_set_drvdata(dev, mctrl);

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (!res) {
		PRT_ERR("failed to get irq resource\n");
		return -EINVAL;
	}
	mctrl->irq = res->start;

	/* clk resource */
	mem_axi_clk = devm_clk_get(dev, "mem_axi_clk");
	if (IS_ERR(mem_axi_clk)) {
		PRT_ERR("get mem axi clk failed\n");
		return -EINVAL;
	}
	mctrl->lat_div_us = clk_get_rate(mem_axi_clk) / 1000000;

	sdram_bw_clk = devm_clk_get(dev, "sdram_bandwidth_clk");
	if (IS_ERR(sdram_bw_clk)) {
		PRT_ERR("get sdram band width clk failed\n");
		return -EINVAL;
	}
	mctrl->sdram_bw_clk = sdram_bw_clk;

	sdram_clk = devm_clk_get(dev, "sdram_clk");
	if (IS_ERR(sdram_clk)) {
		PRT_ERR("get sdram clk failed\n");
		return -EINVAL;
	}
	mctrl->sdram_clk = sdram_clk;

#ifdef CONFIG_PM
	ret = pm_clk_create(&pdev->dev);

	ret = pm_clk_add_clk(&pdev->dev, sdram_bw_clk);

	pm_clk_resume(dev);
#else
	ret = clk_prepare_enable(sdram_bw_clk);
#endif

	for (i = 0; i < mctrl->ch_number; ++i) {
		/* Manual mode */
		csp_memctrl_set_mode(mctrl->reg[i], true);
		csp_memctrl_set_clk(mctrl->reg[i], MEMCTRL_CLK_24M);
		csp_memctrl_set_data_unit(mctrl->reg[i], MEMCTRL_UNIT_1KB);
		csp_memctrl_set_sample(mctrl->reg[i], MEMCTRL_SAMPLE_1MS);
	}

	/* initialize the spinlock */
	spin_lock_init(&mctrl->lock);

	/* Request the IRQ */
	ret = request_irq(mctrl->irq, lombo_memctrl_irq, IRQF_SHARED,
					MEMCTRL_NAME, mctrl);
	if (ret) {
		PRT_ERR("Unable to request IRQ %d\n", mctrl->irq);
		goto out_clk_disable;
	}

	/* Setup the miscdevice */
	mctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
	mctrl->miscdev.name = "memctrl";
	mctrl->miscdev.fops = &lombo_memctrl_fops;

	/* Register the miscdevice */
	ret = misc_register(&mctrl->miscdev);
	if (ret) {
		PRT_ERR("Unable to register miscdevice\n");
		goto out_free_irq;
	}

	/* Create the sysfs files */
	ret = mctrl_conf_sysfs_create_group(mctrl->miscdev.this_device,
							&pdev->dev);
	if (ret) {
		PRT_ERR("Unable to create sysfs files\n");
		goto out_misc_deregister;
	}

	of_mctrl_set_default_bwctrl(dev);
	of_mctrl_set_default_priority(dev);

	ret = mnt_setup(dev);
	if (ret) {
		PRT_ERR("setup monitor failed\n");
		goto out_misc_deregister;
	}

	pm_runtime_set_active(&pdev->dev);
	/* enable the pm_runtime */
	pm_runtime_enable(&pdev->dev);

	return 0;

out_misc_deregister:
	misc_deregister(&mctrl->miscdev);
out_free_irq:
	free_irq(mctrl->irq, mctrl);
out_clk_disable:
#ifdef CONFIG_PM
	pm_clk_suspend(&pdev->dev);
	pm_clk_remove_clk(&pdev->dev, mctrl->sdram_bw_clk);
	pm_clk_destroy(&pdev->dev);
#else
	clk_disable_unprepare(mctrl->sdram_bw_clk);
#endif
	return ret;
}

int lombo_memctrl_remove(struct platform_device *pdev)
{
	struct lombo_memctrl *mctrl = dev_get_drvdata(&pdev->dev);

	mnt_destroy(&pdev->dev);

#ifdef CONFIG_PM
	pm_clk_suspend(&pdev->dev);
	pm_clk_remove_clk(&pdev->dev, mctrl->sdram_bw_clk);
	pm_clk_destroy(&pdev->dev);
#else
	clk_disable_unprepare(mctrl->sdram_bw_clk);
#endif
	misc_deregister(&mctrl->miscdev);
	free_irq(mctrl->irq, mctrl);
	kfree(mctrl->log_buf0);
	kfree(mctrl->log_buf1);
	kfree(mctrl);

	return 0;
}

#ifdef CONFIG_PM
static int lombo_runtime_suspend(struct device *dev)
{
	pm_clk_suspend(dev);

	return 0;
}

static int lombo_runtime_resume(struct device *dev)
{
	pm_clk_resume(dev);

	return 0;
}
#endif

#ifdef CONFIG_PM_SLEEP
static int lombo_suspend(struct device *dev)
{
	pm_clk_suspend(dev);

	return 0;
}

static int lombo_resume(struct device *dev)
{
	if (!pm_runtime_status_suspended(dev))
		pm_clk_resume(dev);

	return 0;
}

static int lombo_suspend_late(struct device *dev)
{
	if (!pm_runtime_status_suspended(dev))
		pm_clk_suspend(dev);

	return 0;
}
#endif

#ifdef CONFIG_PM
static const struct dev_pm_ops lombo_pm_ops = {
	SET_RUNTIME_PM_OPS(lombo_runtime_suspend,
		lombo_runtime_resume, NULL)
#ifdef CONFIG_PM_SLEEP
	SET_SYSTEM_SLEEP_PM_OPS(lombo_suspend,
		lombo_resume)
	.suspend_late = lombo_suspend_late,
#endif
};
#endif

static const struct of_device_id lombo_memctrl_of_match[] = {
	{ .compatible = COMPATIBLE, },
	{},
};

static struct platform_driver lombo_memctrl_driver = {
	.probe		= lombo_memctrl_probe,
	.remove		= lombo_memctrl_remove,
	.driver		= {
		.name		= "lombo-memctrl",
		.of_match_table	= lombo_memctrl_of_match,
		.owner		= THIS_MODULE,
#ifdef CONFIG_PM
		.pm = &lombo_pm_ops,
#endif
	},
};

static int __init lombo_memctrl_init(void)
{
	return platform_driver_register(&lombo_memctrl_driver);
}

static void __exit lombo_memctrl_exit(void)
{
	platform_driver_unregister(&lombo_memctrl_driver);
}

MODULE_AUTHOR("lomboswer@lombotech.com");
MODULE_DESCRIPTION("Driver for LomboTech memctrl controller");
MODULE_LICENSE("GPL");

module_init(lombo_memctrl_init);
module_exit(lombo_memctrl_exit);

